/*
 *  Copyright 2009 Advanced Industrial Science and Technology (AIST).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*! \file
 *  \brief
 */
#pragma once

#include <thrust/graph/detail/undirected_out_edge_iterator_base.hpp>

#include <iostream>

namespace thrust {

namespace detail {
  struct undirected_out_edge_iter_friend;
} // end namespace detail

template <typename EdgeIter,
          typename VertexDescriptor,
          typename EdgeDescriptor,
          typename CalledSpace>
class undirected_out_edge_iter
  : public detail::undirected_out_edge_iter_base<EdgeIter
                                                 , VertexDescriptor
                                                 , EdgeDescriptor
                                                 , CalledSpace
                                                 >::type
{
private:
  typedef undirected_out_edge_iter<EdgeIter
                                   , VertexDescriptor
                                   , EdgeDescriptor
                                   , CalledSpace
                                   > self_t;
  typedef typename detail::undirected_out_edge_iter_base<EdgeIter
                                                         , VertexDescriptor
                                                         , EdgeDescriptor
                                                         , CalledSpace
                                                         >::type super_t;
  friend class thrust::experimental::iterator_core_access;
  friend class detail::undirected_out_edge_iter_friend;
  
public:
  typedef CalledSpace called_space;
  //typedef undirectedS directed_category;
  
public:
  __host__ __device__
  undirected_out_edge_iter() : super_t() {}
  
  __host__ __device__
  undirected_out_edge_iter( const VertexDescriptor& src
                            , const EdgeIter& cur
                            , const EdgeIter& out_end
                            , const EdgeIter& in_begin
                            )
    : super_t( cur)
    , m_src(src)
    , m_cur(cur)
    , m_out_end(out_end)
    , m_in_begin(in_begin)
  {
    if( m_cur ==m_out_end)
      m_cur =m_in_begin;
  }

public:
  __host__ __device__
  inline self_t& operator++() {
    
    if( m_cur ==m_out_end){
      m_cur =m_in_begin;
    }
    else{
      ++m_cur;
      if( m_cur ==m_out_end)
	m_cur =m_in_begin;
    }

    return *this;
  }

  __host__ __device__
  inline bool operator==( const self_t& x) const {
    return (m_cur ==x.m_cur);
  }
  
  __host__ __device__
  inline bool operator!=( const self_t& x) const {
    return (m_cur !=x.m_cur);
  }
  
public:
  __host__ __device__
  inline EdgeDescriptor
  operator*() const {
    return dereference( called_space());
  }
  
private:
  __host__ __device__
  inline EdgeDescriptor
  dereference()
  {
    return dereference( called_space());
  }

  __host__ __device__
  inline EdgeDescriptor
  dereference( thrust::called_from_host_tag) const
  {
    EdgeDescriptor e  =*m_cur;
    if( e.m_source !=m_src){
      e.m_target =e.m_source;
      e.m_source =m_src;
    }
    return e;
  }

  __device__
  inline EdgeDescriptor
  dereference( thrust::called_from_device_tag) const
  {
    //
    // Notice:
    // m_cur.base().get() is *edge_descriptor
    //
    EdgeDescriptor e  =*(m_cur.base().get());
    if( e.m_source !=m_src){
      e.m_target =e.m_source;
      e.m_source =m_src;
    }
    return e;
  }

private:
  VertexDescriptor m_src;
  EdgeIter m_cur;
  EdgeIter m_out_end;
  EdgeIter m_in_begin;
};

} // end namespace thrust
#include <thrust/graph/detail/undirected_out_edge_iterator.inl>
